﻿#region Copyright 2009 by Mike Podonyi, Licensed under the GNU Lesser General Public License
/*  This file is part of Console.Net.

 *  Console.Net is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.

 *  Console.Net is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.

 *  You should have received a copy of the GNU Lesser General Public License
 *  along with Console.Net.  If not, see <http://www.gnu.org/licenses/>.
 */
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Xml.Schema;
using System.Reflection;
using System.Xml.Linq;
using System.Collections.Specialized;
using System.Xml;

namespace Empi.Consolenet
{
    internal class Validation
    {
        internal Validation(DataBucket dataBucket, bool validateForParser,
                            EventHandler<ConsoleValidationEventArgs> validationEventHandler)
        {
            Bucket = dataBucket;
            ValidateForParser = validateForParser;
            ValidationEventHandler = validationEventHandler;
        }

        private readonly DataBucket Bucket;

        private readonly EventHandler<ConsoleValidationEventArgs> ValidationEventHandler;
        private readonly bool ValidateForParser;
        private bool ValidationError=false;
        private bool ValidationEventHandlerHasThrownException = false;

        private void SendValidationEvent(MessageTypes messageType, params string[] messageValues)
        {
            

            SendValidationEvent(new ConsoleValidationException(messageType, messageValues), SeverityType.Error);
        }

        private void SendValidationEvent(MessageTypes messageType,Exception exc)
        {
            if (ValidationEventHandlerHasThrownException)
                throw exc;
            SendValidationEvent(new ConsoleValidationException(messageType,exc), SeverityType.Error);
        }

        

        private void SendValidationEvent(ConsoleValidationException e, SeverityType severity)
        {
            if (severity == SeverityType.Error)
            {
                if (ValidationEventHandler == null)
                {
                    throw e;
                }
                ValidationError = true;
                try
                {
                    ValidationEventHandler(this, new ConsoleValidationEventArgs(e, severity));
                }
                catch 
                {
                    ValidationEventHandlerHasThrownException = true;
                    throw;
                }
            }
            else if (severity == SeverityType.Warning && ValidationEventHandler != null)
            {
                try
                {
                    ValidationEventHandler(this, new ConsoleValidationEventArgs(e, severity));
                }
                catch
                {
                    ValidationEventHandlerHasThrownException = true;
                    throw;
                }
            }
        }


        private void ValidateSchema()
        {
            try
            {
                XmlSchema schema = XmlSchema.Read(
                    Assembly.GetExecutingAssembly().GetManifestResourceStream("Empi.Consolenet.ConsoleNetMain.xsd"),
                    null);

                XmlSchemaSet set = new XmlSchemaSet();

                set.Add(schema);

                Bucket.XCLIStructure.Validate(set, null, ValidateForParser);
            }
            catch (XmlSchemaValidationException exc)
            {
                SendValidationEvent(MessageTypes.ValidationSchema,exc);
                return;
            }
            catch(XmlSchemaException exc)
            {
                SendValidationEvent(MessageTypes.Validation, exc);
                return;
            }
        }

        private void ValidateArgumentsSubElements()
        {
            var foo = (from i in Bucket.XCLIStructure.Descendants(DataBucket.MainNameSpace + "argument")
                       where
                           (i.Attribute("validatortype").Value.Equals("static") &&
                            i.Elements(DataBucket.MainNameSpace + "value").Count() < 2) ||
                           (i.Attribute("validatortype").Value.Equals("regex") &&
                            i.Element(DataBucket.MainNameSpace + "regex") == null) ||
                           (i.Attribute("validatortype").Value.Equals("custom") &&
                            i.Element(DataBucket.MainNameSpace + "custom") == null)
                       select i).FirstOrDefault();

            if (foo != null)
            {
                SendValidationEvent(MessageTypes.WrongArgumentSubElements, foo.Attribute("validatortype").Value);
            }
        }

        internal bool Validate()
        {
            try
            {
                Action[] funcs =
                    {
                        ValidateSchema,
                        ValidateArgumentsSubElements,
                        //ValidateOptionArguments,
                        //ValidateOptionArguments2,
                        //ValidateOptionArguments3,
                        //ValidateOptionArguments4,
                        //ValidateOptionArguments5,
                        //ValidateOptionArguments6,
                        //ValidateOptionArguments7,
                        //ValidateCommandValues,
                        BetterValidation
                    };
                
                foreach (var func in funcs)
                {
                    if (!ValidationError)
                        func();
                }
                
            }
            catch (Exception exc)
            {
                if(!(exc is ConsoleValidationException))
                {
                    SendValidationEvent(MessageTypes.UnknownError, exc);
                }
                else
                {
                    throw;
                }
            }

            return ValidationError;
        }

        //private void ValidateOptionArguments2()
        //{
        //    foreach (var xCommand in Bucket.XCLIStructure.Descendants(DataBucket.MainNameSpace + "command").Concat(Bucket.XCLIStructure.Descendants(DataBucket.MainNameSpace + "rootcommand")))
        //    {

        //        var optionpervaluecollection =
        //            from i in
        //                xCommand.Elements(DataBucket.MainNameSpace + "options").Elements(DataBucket.MainNameSpace + "option").
        //                Concat
        //                (Bucket.XGlobalOptions)
        //            where i.Element(DataBucket.MainNameSpace + "argument") != null
        //            from i2 in i.Attribute("value").Value.SplitMe()
        //            group i by i2
        //            into v
        //                where v.Count() > 1
        //                orderby v.Count() descending
        //                select v;

        //        

        //        foreach (var options in optionpervaluecollection)
        //        {
        //            var key = options.Key;
        //            bool regexValueFlag = false;
        //            StringCollection sc = new StringCollection();

        //            foreach (var option in options)
        //            {
        //                var argument =
        //                    option.Element(DataBucket.MainNameSpace + "argument");
        //                if (argument.Attribute("validatortype").Value == "regex")
        //                {
        //                    if (regexValueFlag)
        //                    {
        //                        SendValidationEvent(MessageTypes.DoubleRegex, key);
        //                        return;
        //                    }
        //                    else
        //                        regexValueFlag = true;
        //                }
        //                else if (argument.Attribute("validatortype").Value == "static")  
        //                {
        //                    foreach (var argumentvalue in argument.Elements(DataBucket.MainNameSpace + "value"))
        //                    {
        //                        var staticvalue = argumentvalue.Attribute("value").Value;
        //                        if (sc.Contains(staticvalue))
        //                        {
        //                            SendValidationEvent(MessageTypes.StaticValidatorValueClash, key, staticvalue);
        //                            return;
        //                        }
        //                        else
        //                        {
        //                            sc.Add(staticvalue);
        //                        }
        //                    }
        //                }
        //            }
        //        }
        //    }
        //}

        /// <summary>
        /// Does the following:
        /// Checks that only option with the same value exist with no subarguments
        /// or 
        /// Otions with the same value exist with no subarguments
        /// </summary>
        /// <returns>An error object in case that the XML Configuration could not fulfill the Validation. Or Null.</returns>
        //private void ValidateOptionArguments()
        //{
        //    foreach (var xCommand in Bucket.XCLIStructure.Descendants(DataBucket.MainNameSpace + "command").Concat(Bucket.XCLIStructure.Descendants(DataBucket.MainNameSpace + "rootcommand")))
        //    {

        //        string[] errorOptionValues =
        //            (from i in
        //                 xCommand.Elements(DataBucket.MainNameSpace + "options").Elements(DataBucket.MainNameSpace + "option").
        //                 Concat(Bucket.XGlobalOptions)
        //             where i.Element(DataBucket.MainNameSpace + "argument") != null
        //             from i2 in i.Attribute("value").Value.SplitMe()
        //             select i2).Intersect(
        //                from i in
        //                    xCommand.Elements(DataBucket.MainNameSpace + "options").Elements(DataBucket.MainNameSpace + "option")
        //                    .
        //                    Concat(Bucket.XGlobalOptions)
        //                where i.Element(DataBucket.MainNameSpace + "argument") == null
        //                from i2 in i.Attribute("value").Value.SplitMe()
        //                select i2).ToArray();

        //        if (errorOptionValues.Count() > 0)
        //        {
        //            SendValidationEvent(MessageTypes.OptionWithoutParams, errorOptionValues);
        //            return;
        //        }
        //    }
        //}

        //private void ValidateOptionArguments3()
        //{
        //    foreach (var xCommand in Bucket.XCLIStructure.Descendants(DataBucket.MainNameSpace + "command").Concat(Bucket.XCLIStructure.Descendants(DataBucket.MainNameSpace + "rootcommand")))
        //    {

        //        string[] errorOptionValues =
        //            (from i in
        //                 xCommand.Elements(DataBucket.MainNameSpace + "options").Elements(DataBucket.MainNameSpace + "option").
        //                 Attributes("name")
        //             select i.Value).Intersect(
        //                from i in Bucket.XGlobalOptions.Attributes("name")
        //                select i.Value).ToArray();

        //        if (errorOptionValues.Count() > 0)
        //        {
        //            SendValidationEvent(MessageTypes.OptionNameOverridesGlobalOptionName, errorOptionValues);
        //            return;
        //        }
        //    }
        //}

        //private void ValidateOptionArguments4()
        //{
            //foreach (var xCommand in Bucket.XCLIStructure.Descendants(DataBucket.MainNameSpace + "command").Concat(Bucket.XCLIStructure.Descendants(DataBucket.MainNameSpace + "rootcommand")))
            //{

            //    string[] errorOptionValues =
            //        (from i in
            //             xCommand.Elements(DataBucket.MainNameSpace + "options").Elements(DataBucket.MainNameSpace + "option").
            //             Concat(Bucket.XGlobalOptions).Attributes("value")
            //         from i2 in i.Value.SplitMe()
            //         select i2).Intersect(
            //            from i in
            //                xCommand.Elements(DataBucket.MainNameSpace + "commands").Elements(DataBucket.MainNameSpace +
            //                                                                              "command").
            //                Attributes("value")
            //            from i2 in i.Value.SplitMe()
            //            select i2).ToArray();

            //    if (errorOptionValues.Count() > 0)
            //    {
            //        SendValidationEvent(MessageTypes.OptionValueOverridesCommandValue, errorOptionValues);
            //        return;
            //    }
            //}
        //}

        //private void ValidateOptionArguments5()
        //{
            //foreach (var xCommand in Bucket.XCLIStructure.Descendants(DataBucket.MainNameSpace + "command").Concat(Bucket.XCLIStructure.Descendants(DataBucket.MainNameSpace + "rootcommand")))
            //{

            //    var errorOptionValues =
            //        (from i in
            //             xCommand.Elements(DataBucket.MainNameSpace + "options").Elements(DataBucket.MainNameSpace + "option").
            //             Concat(Bucket.XGlobalOptions)
            //         where i.Element(DataBucket.MainNameSpace + "argument") == null
            //         from i2 in i.Attribute("value").Value.SplitMe()
            //         group i by i2
            //         into v
            //             where v.Count() > 1
            //             select v).FirstOrDefault();

            //    if (errorOptionValues != null)
            //    {
            //        SendValidationEvent(MessageTypes.MultipleOptionValuesWithoutArguments, errorOptionValues.Key);
            //        return;
            //    }
            //}
        //}

        //private void ValidateOptionArguments6()
        //{
        //    var errorOptionValues = (from i in Bucket.XCLIStructure.Descendants(DataBucket.MainNameSpace + "option").Concat(Bucket.XGlobalOptions)
        //                             where i.Element(DataBucket.MainNameSpace + "argument") != null
        //                             let iivals = i.Attribute("value").Value.SplitMe()
        //                             from ivals in iivals
        //                             from ivals2 in iivals
        //                             where !ivals.Equals(ivals2) && ivals.StartsWith(ivals2)
        //                             select new { i.Attribute("name").Value, ivals, ivals2 }).FirstOrDefault();

        //    if (errorOptionValues != null)
        //    {
        //        SendValidationEvent(MessageTypes.OptionAliasValueStartWithOtherAliasValue, errorOptionValues.Value,
        //                            errorOptionValues.ivals, errorOptionValues.ivals2);
        //    }
        //}

        //private void ValidateOptionArguments7()
        //{
            //foreach (var xCommand in Bucket.XCLIStructure.Descendants(DataBucket.MainNameSpace + "command").Concat(Bucket.XCLIStructure.Descendants(DataBucket.MainNameSpace + "rootcommand")))
            //{

            //    var errorOptionValues =
            //        (from i in
            //             xCommand.Elements(DataBucket.MainNameSpace + "options").Elements(DataBucket.MainNameSpace + "option").
            //             Concat(Bucket.XGlobalOptions)
            //             let yy=i.Element(DataBucket.MainNameSpace + "argument")

            //         where yy!=null && yy.Attribute("argumentOptional") !=null && Convert.ToBoolean(yy.Attribute("argumentOptional").Value) 
            //         from i2 in i.Attribute("value").Value.SplitMe()
            //         group i by i2 into v
            //             where v.Count() > 1
            //             select new []{v.Key}.Concat(v.Select((x)=>x.Attribute("name").Value)).ToArray()).FirstOrDefault();
                
            //    if (errorOptionValues != null)
            //    {
            //        SendValidationEvent(MessageTypes.DoubleArgumentOptionalWithSameOptionValue, errorOptionValues);
            //        return;
            //    }
            //}
        //}

        //private void ValidateCommandValues()
        //{
            //foreach (var xCommand in Bucket.XCLIStructure.Descendants(DataBucket.MainNameSpace + "command").Concat(Bucket.XCLIStructure.Descendants(DataBucket.MainNameSpace + "rootcommand")))
            //{
            //    var errorCommandValues =
            //        (from i in
            //             xCommand.Elements(DataBucket.MainNameSpace + "commands").Elements(DataBucket.MainNameSpace + "command")
            //         from i2 in i.Attribute("value").Value.SplitMe()
            //         group i by i2
            //         into v
            //             where v.Count() > 1
            //             select v).FirstOrDefault();

            //    if (errorCommandValues != null)
            //    {
            //        SendValidationEvent(MessageTypes.CommandValueOverridesCommandValue, errorCommandValues.Key);
            //        return;
            //    }
            //}
        //}

        private void BetterValidation()
        {
            foreach (var xCommand in Bucket.XCLIStructure.Descendants(DataBucket.MainNameSpace + "command").Concat(Bucket.XCLIStructure.Descendants(DataBucket.MainNameSpace + "rootcommand")))
            {
                var commands =
                    from i in
                        xCommand.Elements(DataBucket.MainNameSpace + "commands").Elements(DataBucket.MainNameSpace +
                                                                                          "command")
                    from i2 in i.Attribute("value").Value.SplitMe()
                    select new {Key=i2, Value=i};

                var optionsWOargument =
                    from i in
                        xCommand.Elements(DataBucket.MainNameSpace + "options").Elements(DataBucket.MainNameSpace +
                                                                                         "option").Concat(
                        Bucket.XGlobalOptions)
                    let arg = i.Element(DataBucket.MainNameSpace + "argument")
                    where arg == null || ((bool?) arg.Attribute("argumentOptional")) == true
                    from i2 in i.Attribute("value").Value.SplitMe()
                    select new {Key=i2, Value=i};

                var optionsWargument =
                    from i in
                        xCommand.Elements(DataBucket.MainNameSpace + "options").Elements(DataBucket.MainNameSpace +
                                                                                         "option").Concat(
                        Bucket.XGlobalOptions)
                    let arg = i.Element(DataBucket.MainNameSpace + "argument")
                    where arg != null && arg.Attribute("validatortype").Equals("static")
                    from i1 in i.Attribute("value").Value.SplitMe()
                    from i2 in arg.Attribute("splitter").Value.SplitMe()
                    from i3 in arg.Elements(DataBucket.MainNameSpace + "value")
                    let iback=i1+ i2 + i3.Attribute("value")
                    select new {Key=iback, Value=i};

                var joint = (from i in commands.Concat(optionsWOargument).Concat(optionsWargument)
                            group i.Value by i.Key
                            into v
                                where v.Count()>1
                                //orderby v.Count() descending
                                select v).FirstOrDefault();

                if(joint != null)
                {  
                    SendValidationEvent(MessageTypes.AmbigousValues, joint.Key, GetNamePath(joint.ElementAt(0)), GetNamePath(joint.ElementAt(1)));
                }
           }

        }

        private string GetNamePath(XElement xelement)
        {
            return xelement.Attribute("name").Value;
        }
    }
}